﻿/*!
* jQuery.bindTools JavaScript Library v2.0
* http://code.google.com/p/ornamentframework
*
* Copyright 2010
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://code.google.com/p/ornamentframework/wiki/LICENSE
*
* Date: 2011-01-01
*/
/// <reference path="jquery.min-vsdoc.js" />
// convertion
(function($) {

    $.fn.bindTool = function(options) {
        var result = $(this),
        args = arguments, optionsKey = "bindTool";
        result.each(function() {
            var target = $(this);
            //execute command 
            if (typeof options == "string") {
                var commandArgs = [target.data(optionsKey)].concat([].slice.call(args, 1));
                result = method[options].apply($(this), commandArgs);

            } else {
                var opts = $.extend({}, $.fn.bindTool.defaults, options);
                target.data(optionsKey, opts);
            }
        });
        return result;
    };

    $.fn.bindTool.defaults = {
        way: false, //[ {member: "对象的成员，支持property.property的方式", to: 绑定控件的方式
        //，可以是html id，或者jquery对象，或者"#id.attrbuteName", from: 返回的数据对象}]
        template: false, //bind的对象是否为template，默认为false
        bindToContent: function(templateIns) { $(templateIns).appendTo(this); }, //template完成数据绑定,加入content的方法;       
        show: function(beArea) {//当template,bind已经完成，准备显示的时候
            $(beArea).fadeIn('slow');
        },
        mergeWay: true, //bind way auto merge from setting.
        onBind: false, //bind json之前
        onBound: false, //after each json object bound
        beforeBind: false, //before bind.
        afterBound: false, //after bound,
        convert: {
            boolExp: { "true": true, "false": false },
            dateFormat: function(dateVal) { //在Html呈现的时候，使用的格式 默认是2009-10-10                
                return dateVal.getFullYear() + "-" + (dateVal.getMonth() + 1) + "-" + dateVal.getDate();
            },
            dateExp: [
            {
                pattern: /(^\/Date\(\d*\)\/$)/, //asp.net 日期格式
                toObj: function(v) { return eval("new " + v.substr(1, v.length - 2)); }
            },
            {
                pattern: /\d{4}-\d{1,2}-\d{1,2}/, //匹配2009-1-1
                toObj: function(val) {
                    var stAr = val.split('-');
                    return new Date(parseInt(stAr[0]), (parseInt(stAr[1]) - 1), parseInt(stAr[2]));
                }
            }
            ],
            toObj: function(strValue, type) { // string to Object;                
                var ary = type.split("-"), self = this;
                return ary.length > 1 ? self["array"](strValue, ary[1]) : self[type]($.trim(strValue));
            },
            toStr: function(objValue) { // obj to str;

                if (objValue == undefined) { return undefined; }
                var self = this, type = self.getType(objValue).split("-");
                switch (type[0]) {
                    case "date":
                        return self.dateFormat(self.date(objValue));
                    case "array":
                        var result = [];
                        $.each(objValue, function() {
                            result.push(self.toStr(this));
                        });
                        return result;
                    default:
                        return objValue.toString();
                }
            },
            getType: function(obj) {
                var self = this;

                function isDate() {
                    if (obj instanceof Date) {
                        return true;
                    }
                    var result = false;
                    $.each(self.dateExp, function() {
                        if (this.pattern.test(obj)) {
                            result = true;
                            return false;
                        }
                    });
                    return result;
                }

                if (obj instanceof Array) {
                    return "array-" + self.getType(obj[0]);
                }

                return isDate() ? "date" : typeof obj;
            },
            //following is set str to obj
            'string': function(strValue) {
                return strValue;
            },
            'number': function(strValue) {
                return parseFloat(strValue);
            },
            'boolean': function(strValue) {
                return this.boolExp[strValue];
            },
            'date': function(obj) {
                var type = typeof obj;
                if (type == "string") {
                    var exps = this.dateExp;
                    for (var i = 0; i < exps.length; i++) {
                        var exp = exps[i];
                        if (exp.pattern.test(obj)) {
                            return exp.toObj(obj);
                        }
                    }
                    var result = Date.parse(obj);
                    return isNaN(result) ? false : new Date(result);
                }
                if (type == "object" && obj instanceof Date) {
                    return obj;
                }
                return false;
            },
            'array': function(strValue, type) {
                var result = [], self = this;
                for (var i = 0; i < strValue.length; i++) {
                    result.push(self.toObj(strValue[i], type));
                }
                return result;
            }
        }

    };

    var htmlAcc = {
        INPUT: function(target, data) {
            var getData = data === undefined;
            switch (target.attr("type")) {
                case "radio":
                    var val = null;
                    target.each(function() {
                        var ctrl = $(this);
                        if (getData) {
                            if (ctrl.attr("checked")) {
                                val = ctrl.val();
                                return false;
                            }
                        }
                        ctrl.attr("checked", ctrl.val() == data);
                    });
                    return val;
                case "checkbox":
                    var result = getData ? [] : data;
                    if (!$.isArray(result)) {
                        result = $.makeArray(result);
                    }
                    $(target).each(function() {
                        var self = $(this);
                        if (getData) {
                            if (this.checked) {
                                result.push(self.val());
                            }
                        }
                        else {
                            self.attr("checked", $.inArray(self.val(), data) != -1);
                        }
                    });
                    return result;
                default:
                    return target.val(data);
            }
        },
        SELECT: function(target, data) {
            if (data !== undefined) {
                target.children().removeAttr("selected");
            }
            return target.val(data);
        },

        TEXTAREA: function(target, data) {
            return target.val(data);
        },
        text: function(target, data) {
            return target.text(data);
        }
    }
    function attr(target, data) {
        var func = htmlAcc[target[0].nodeName];
        return (func) ? func(target, data) : htmlAcc.text(target, data);
    }

    function findCtrl(area, selector, childPrefix, bTryGet) {
        if (!selector) {
            return undefined;
        }
        if (selector.indexOf("#") == 0 && childPrefix) {
            selector = "#" + childPrefix + selector.substr(1);
        }
        var a = $(selector, area);
        if (a.length == 0) {
            if (bTryGet) {
                a = undefined;
            } else {
                alert("Not found target use " + selector);
            }
        }
        return a;
    }
    function jsonToWay(jObj, ary, parentKey) {
        if (ary == undefined) {
            ary = [];
        }
        for (var key in jObj) {
            var newKey = parentKey == undefined ? key : parentKey + "." + key,
                 v = jObj[key];
            if (typeof v == "object" && !$.isArray(v) && !(v instanceof Date) && v != null) {
                jsonToWay(v, ary, newKey);
            }
            else {
                ary.push({ member: newKey });
            }
        }
        return ary;
    }


    function fireEvent(thisPointer, fun) {
        return $.isFunction(fun) ? fun.apply(thisPointer, [].slice.call(arguments, 2)) : undefined;
    }
    //Bind To opts.Template target, and this is Tempalte.
    //this pinter is template model.
    function tmpBind(jObject, opts, cOpts, prefix) {
        var tmp = this, templateId = tmp.attr("id"), searchArea = tmp.clone();
        searchArea.data("prefix", prefix);

        if (templateId != "") {//if temp has id,need to change the id
            searchArea.attr("id", prefix.substr(0, prefix.length - 1));
        }
        return tagBind.call(searchArea, jObject, opts, cOpts, "", prefix);


    }
    function gv(o, p) { return eval("o." + p); };

    function eachBind(searchArea, way, value, opts, childTargetPrefix) {
        var defaultSelector = "[name=" + way.member + "]";
        if (way.to && $.isFunction(way.to)) {
            var target = findCtrl(searchArea, way.member ? defaultSelector : undefined, childTargetPrefix, true);
            return fireEvent(searchArea, way.to, value, target);
        }
        var bindTarget = findCtrl(searchArea, way.to ? way.to : defaultSelector, childTargetPrefix);
        attr(bindTarget, opts.convert.toStr(value));
        return bindTarget;
    };

    //Bind to panel. not for tempalte
    // this poiner is the area to be bound.
    function tagBind(jObject, opts, cOpts, childTargetPrefix, newPrefix) {

        var searchArea = this,
         waies = cOpts.way ? cOpts.way : opts.way;

        //here fire onbind on the cOption,but the onBound have to fire event in method.bind function
        fireEvent(searchArea, cOpts.onBind, jObject);

        $.each(waies, function() {
            var bindTarget, value = jObject, way = this;
            if (way.to === false) {
                return true;
            }
            if (way.member) {
                value = gv(jObject, way.member);
                if (!way.type) {
                    way.type = opts.convert.getType(value);
                }
            }

            bindTarget = eachBind(searchArea, way, value, opts, childTargetPrefix);
            if (!bindTarget)
                return true;
            if (newPrefix && bindTarget.attr("id") != "") {
                bindTarget.attr("id", newPrefix + bindTarget.attr("id"));
            }
        });



        return searchArea;
    }

    //templateNameOrTemplateIns on templateName to find area will be bound.
    function getRealArea(searchRang, opts, templateNameOrTemplateIns) {
        var returnArray = false;
        if (opts.template) {
            if (templateNameOrTemplateIns && templateNameOrTemplateIns.jquery) {
                searchRang = templateNameOrTemplateIns;
                returnArray = (templateNameOrTemplateIns).jquery && templateNameOrTemplateIns.length > 1;
            }
            else {
                var tmpModel = templateNameOrTemplateIns ? $.makeArray(opts[templateNameOrTemplateIns].template) : opts.template,
                selector = [];
                $(tmpModel).each(function() {
                    var self = $(this), idx = self.data("idx");
                    if (idx == 0) { return true; }
                    for (var i = 1; i <= idx; i++) {
                        selector.push("#" + self.attr("id") + "_" + i);
                    }
                });
                searchRang = $(selector.join(","), searchRang);
                returnArray = true;
            }
        }
        return { area: searchRang, aryResult: returnArray };
    };

    function buildPrefix(template, switchName) {
        var a = template.data("idx"),
         tmpId = template.attr("id");
        if (a == undefined) {
            a = 0;
        }
        template.data("idx", ++a);

        if (!tmpId) {
            tmpId = switchName;
        }
        return tmpId + "_" + a + "_";
    }

    function iniWay(opts, jsonObject) {
        var bindWayFromjObj = jsonToWay($.isArray(jsonObject) ? jsonObject[0] : jsonObject), memberAry = [], newWay = []; ;
        opts.way = opts.way ? opts.way.concat(bindWayFromjObj) : bindWayFromjObj;
        $.each(opts.way, function(i, n) {
            var result = $.inArray(n.member, memberAry);
            if (result < 0) {
                memberAry.push(n.member);
                newWay.push(n);
            }
        });
        opts.way = newWay;
    };

    //bindTool's this pointer always is $(html);      
    var method = {
        bind: function(opts, jsonObject, templateIns) {
            var bindTarget = $(this),
                isTmpIns = templateIns != undefined, result = false, cOpts = opts, tempName,
             bindResult = fireEvent(bindTarget, opts.beforeBind, jsonObject);

            if (typeof bindResult == "object") {
                jsonObject = bindResult;
            }
            else if (bindResult === false) {
                return bindTarget;
            }

            if (!opts.way || (opts.mergeWay && !opts.iniBindWay)) {
                iniWay(opts, jsonObject);
                opts.iniBindWay = true;
            }
            //cOpts = opts;
            if (isTmpIns) {
                bindTarget = templateIns;
                tempName = templateIns.data("template");
                cOpts = tempName == "template" ? opts : opts[tempName];
            }

            var newTemplate = opts.template && !isTmpIns, prefix,
                onbound = cOpts.onBound ? cOpts.onbound : opts.onBound;
            result = [];
            $(jsonObject).each(function() {
                var obj = this, beBindArea;
                if (newTemplate) {
                    prefix = buildPrefix(opts.template, tempName);
                    beBindArea = tmpBind.call(opts.template, obj, opts, cOpts, prefix).data("template", "template"); //use default template. 
                    if (beBindArea) {
                        fireEvent(bindTarget, opts.bindToContent, beBindArea);
                        result.push(beBindArea[0]);
                    }

                }
                else {//bind to target
                    prefix = bindTarget.data("prefix");
                    if (!prefix) {
                        prefix = "";
                    }
                    result = beBindArea = $(tagBind.call(bindTarget, obj, opts, cOpts, prefix));
                }
                fireEvent(bindTarget, cOpts.onBound, obj, beBindArea);
            });
            if (newTemplate) {
                fireEvent(bindTarget, opts.show, result);
            }
            fireEvent(this, opts.afterBound, jsonObject);
            return result;
        },
        collect: function(opts, templateNameOrTemplateIns) {
            var area = getRealArea(this, opts, templateNameOrTemplateIns), result = [];

            $.each(area.area, function() {
                var jsonObject = {},
                 inSearchRang = $(this),
                 prefix = inSearchRang.data("prefix"), waies = opts.way;

                if (templateNameOrTemplateIns != undefined) {
                    var tempName = templateNameOrTemplateIns.jquery ? $(templateNameOrTemplateIns).data("template") : templateNameOrTemplateIns;

                    if (tempName != undefined && tempName != "template" && opts[tempName].way != undefined) {
                        waies = opts[tempName].way
                    }
                }

                $.each(waies, function() {
                    var way = this, target, value;
                    if (way.from === false) {
                        return true;
                    }
                    if (way.from && $.isFunction(way.from)) {
                        target = findCtrl(inSearchRang, way.member ? "[name=" + way.member + "]" : undefined, prefix, true);
                        value = fireEvent(inSearchRang, way.from, jsonObject, target);
                        if (value === undefined) {
                            return true;
                        }

                    }

                    if (value == undefined) {
                        if (target == undefined) {
                            target = findCtrl(inSearchRang, way.from ? way.from : (!way.to || $.isFunction(way.to)) ? "[name=" + way.member + "]" : way.to, prefix);
                        }
                        value = opts.convert.toObj(attr(target), way.type);
                    }

                    var setObjVal = function(jsonExp, value) {
                        var aryKeys = jsonExp.split("."), propertyName = aryKeys[aryKeys.length - 1], currentObject = jsonObject;
                        $.each(aryKeys, function(i) {
                            if (i == aryKeys.length - 1)
                                return false;
                            var objKey = this;

                            if (currentObject[objKey] == undefined) {
                                currentObject[objKey] = {};
                            }
                            currentObject = jsonObject[objKey];
                        });
                        currentObject[propertyName] = value;
                    };
                    setObjVal(way.member, value);

                });
                result.push(jsonObject);
            });
            /*
            * 返回当个对象
            1)没有使用Template功能
            2)使用Template功能，但是只收集一个 template 实例的对象的时候。
            * 返回多个数组
            1) 使用Template功能，收集所有template实例数据的时候。
            2) 指定收集某个区间的template实例。并且大于一个。
            */
            fireEvent(this, arguments[arguments.length], result);
            return area.aryResult ? result : result[0];
        },
        "switch": function(opts, switchTemplateName, switchArea) {

            var keySrcPrefix = "srcPrefix", keyTemplate = "template",
                srcPrefix = switchArea.data(keySrcPrefix), prefix,
                cOpts = switchTemplateName == "template" ? opts : opts[switchTemplateName],
            targetTemp = cOpts.template;

            if (!srcPrefix) {
                srcPrefix = {};
            }
            srcPrefix[switchArea.data(keyTemplate)] = switchArea.data("prefix");
            prefix = srcPrefix[switchTemplateName];
            if (!prefix) {
                prefix = buildPrefix(cOpts.template, switchTemplateName);
            }

            var jsonObj = $(this).bindTool('collect', switchArea),
                bindTemp = tmpBind.call(targetTemp, jsonObj, opts, cOpts, prefix).data(keyTemplate, switchTemplateName).data(keySrcPrefix, srcPrefix);
            fireEvent(targetTemp, cOpts.onBound, jsonObj, bindTemp);

            if (bindTemp) {
                switchArea.replaceWith(bindTemp);
                opts.show(bindTemp);
            }
        },
        option: function(opts, property, val) {
            if (val == undefined) {
                return opts[property];
            }
            opts[property] = value;
        }        
    };


})($);

